package org.apache.ibatis.io;

import java.io.InputStream;
import java.net.URL;

/**
 * 真正加载mybatis配置文件的类
 * 主要是封装了classLoader，跟据classLoader优先级去加载资源
 *
 * classLoader简单介绍，级别从高到底
 * 1、Bootstrap ClassLoader
 * 主要加载/lib目录下类库到虚拟机中，加载java核心类库，不能由java程序直接调用，不继承于java.lang.ClassLoader，由C++编写，属于虚拟机自身的一部分
 * 2、Extendsion ClassLoader
 * 主要加载/lib/ext目录下的类库，ext目录下是java的扩展库，可以由java程序直接调用
 * 3、Application ClassLoader
 * 主要加载用户类路径的类库，即CLASSPATH，开发人员开发的类都是由这个加载，默认类加载器，可以由ClassLoader.getSystemClassLoader获取
 * @author xieqiaofu
 * Create time: 2019/10/08
 **/
public class ClassLoaderWrapper {

    /**
     * 默认的classLoader
     */
    ClassLoader defaultClassLoader;

    /**
     * java虚拟机
     */
    ClassLoader systemClassLoader;

    ClassLoaderWrapper() {
        try {
            systemClassLoader = ClassLoader.getSystemClassLoader();
        } catch (SecurityException e) {
            //权限异常，可忽略
        }
    }

    /**
     * 从当前目录，即CLASSPATH下获取一个URL资源
     * @param resource
     * @return
     */
    public URL getResourceAsURL(String resource) {
        return getResourceAsURL(resource, getClassLoaders(null));
    }

    /**
     * 从当前目录，即CLASSPATH下获取一个URL资源
     * @param resource
     * @param classLoader 使用者自己传入的classLoader
     * @return
     */
    public URL getResourceAsURL(String resource, ClassLoader classLoader) {
        return getResourceAsURL(resource, getClassLoaders(classLoader));
    }

    /**
     * 从当前目录，即CLASSPATH下获取一个InputStream资源
     * @param resource
     * @return
     */
    public InputStream getResourceAsStream(String resource) {
        return getResourceAsStream(resource, getClassLoaders(null));
    }

    public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
        return getResourceAsStream(resource, getClassLoaders(classLoader));
    }

    private InputStream getResourceAsStream(String resource, ClassLoader[] classLoaders) {
        for (ClassLoader cl : classLoaders) {
            if (null != cl) {
                InputStream returnValue = cl.getResourceAsStream(resource);
                if (null == returnValue) {
                    returnValue = cl.getResourceAsStream("/" + resource);
                }
                if (null != returnValue) {
                    System.out.println("load resource completed, cl:" + cl + ", resource:" + resource);
                    return returnValue;
                }
            }
        }
        return null;
    }

    private URL getResourceAsURL(String resource, ClassLoader[] classLoaders) {
        URL url = null;
        for (ClassLoader cl : classLoaders) {
            if (null != cl) {
                url = cl.getResource(resource);
            }
            //如果没有加载到资源，就在前面加/再加载一次
            if (null == url) {
                url = cl.getResource("/" + resource);
            }
            //如果这个classLoader加载不到，就用下一个classLoader去加载
            if (null != url) {
                return url;
            }
        }
        return null;
    }

    /**
     * 这个方法很巧妙，按照classLoader的优先级从高到底依次放入数组中，遍历查找文件时会按照classLoader优先级去查找
     * 1、方法参数classLoader为最高优先级，是用户调用方法时传入的classLoader
     * 2、defaultClassLoader是第二优先级，用户可以从Resources类传入classLoader
     * 3、当前线程加载类的classLoader。此classLoader优先级要高于getClass().getClassLoader()。多线程情况下，每个线程都有可能有自己的classLoader
     * 4、虚拟机加载class文件的classLoader
     * 5、application classLoader
     * @param classLoader
     * @return
     */
    ClassLoader[] getClassLoaders(ClassLoader classLoader) {
        return new ClassLoader[] {
            classLoader,
            defaultClassLoader,
            Thread.currentThread().getContextClassLoader(),
            getClass().getClassLoader(),
            systemClassLoader
        };
    }
}
